/*
 * Copyright 2008 Max Kugland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.splink.asbeangen.parser;

import java.io.File;
import java.util.Enumeration;
import java.util.List;

import org.splink.asbeangen.parser.iface.InterfaceBuilder;
import org.splink.asbeangen.parser.utils.ClassFileWriter;
import org.splink.asbeangen.parser.utils.CommentMapper;
import org.splink.asbeangen.templates.ITemplateClass;
import org.splink.asbeangen.templates.ITemplateDataClass;
import org.splink.asbeangen.templates.ITemplateFactory;
import org.splink.asbeangen.templates.ITemplateReaderClass;

import com.wutka.dtd.DTD;
import com.wutka.dtd.DTDAttribute;
import com.wutka.dtd.DTDElement;
import com.wutka.dtd.DTDItem;
import com.wutka.dtd.DTDParser;

/**
 * Not supported are: Notations, Entities, Processing Instructions (Include,
 * Ignore)
 * 
 * Note that only doubly clamped choices get recognized, i.e. : ((Text |
 * Table)) Otherwise it seems that the DTD library can't detect the choice.
 * Choices carrying a qualifier are detected fine.
 * 
 * @author maxmc
 */
public class Parser {
	private String _srcPath;
	private DTD _dtd;
	private CommentMapper _commentMapper;
	private ClassFileWriter _classFileWriter;
	private String _basePackage;
	private List<String> _pcDataElements;
	private SubelementParser _subelementParser;
	private ITemplateFactory _templateFactory;

	public Parser(String srcPath, String targetPath, String basePackage, ITemplateFactory templateFactory) {
		_srcPath = srcPath;
		_basePackage = basePackage;
		_templateFactory = templateFactory;
		if (_basePackage != "")
			_basePackage += ".";

		_classFileWriter = new ClassFileWriter(targetPath);

		try {
			parse();
		} catch (Exception e) {
			System.out.println("AsBeanGen Exeption " + e.getMessage() + " - " + e.toString());
		}
	}

	private void parse() throws Exception {
		DTDParser parser = new DTDParser(new File(_srcPath), false);
		_dtd = parser.parse(true);

		if (_dtd.rootElement != null) {
			_commentMapper = new CommentMapper(_dtd.items.elements());
			_pcDataElements = ParseTools.getPCDElements(_dtd);
			_subelementParser = new SubelementParser(this, _commentMapper, _pcDataElements, _classFileWriter);
			parseRoot(_dtd.rootElement);

			InterfaceBuilder ifaceBuilder = parseInterfaces();
			_classFileWriter.addInterfaces(ifaceBuilder.getInterfaceStack());
			_classFileWriter.write();
			System.out.println("done.");
		} else {
			System.out.println("DTD seems to be invalid, could not detect a root element.");
			return;
		}
	}

	private InterfaceBuilder parseInterfaces() {
		return new InterfaceBuilder(_templateFactory.getCopyright(), _templateFactory.getInterfaceBasepackage(),
				_commentMapper.getInterfaces(), _classFileWriter.getClazzStack(), _templateFactory.getClassTemplate());
	}

	private void parseRoot(DTDElement root) {
		String name = ParseTools.clean(root.name);

		ITemplateDataClass dataClazz = _templateFactory.createDataClass(_basePackage, name, _commentMapper
				.getInterfacesForClass(name));
		ITemplateReaderClass readerClazz = _templateFactory.createReaderClass(_basePackage, name, _commentMapper
				.getInterfacesForClass(name));

		parseAttributes(root, _basePackage, dataClazz, readerClazz);
		_subelementParser.parseSubelements(root.content, _basePackage, dataClazz, readerClazz);

		_classFileWriter.addClazz(dataClazz);
		_classFileWriter.addClazz(readerClazz);
	}

	protected void parseElement(DTDItem item, String classpath) {
		String name = ParseTools.getName(item);
		ITemplateDataClass dataClazz = _templateFactory.createDataClass(classpath, name, _commentMapper
				.getInterfacesForClass(name));
		ITemplateReaderClass readerClazz = _templateFactory.createReaderClass(classpath, name, _commentMapper
				.getInterfacesForClass(name));

		classpath += name + ".";
		Enumeration<?> elements = _dtd.elements.elements();
		while (elements.hasMoreElements()) {
			DTDElement elem = (DTDElement) elements.nextElement();
			if (ParseTools.clean(elem.name).equals(name)) {
				parseAttributes(elem, classpath, dataClazz, readerClazz);
				_subelementParser.parseSubelements(elem.content, classpath, dataClazz, readerClazz);

				if (_classFileWriter.getClazzByName(name) == null) {
					_classFileWriter.addClazz(dataClazz);
					_classFileWriter.addClazz(readerClazz);
				}
			}
		}
	}

	private void parseAttributes(DTDElement element, String classpath, ITemplateDataClass dataClazz,
			ITemplateReaderClass readerClazz) {
		Enumeration<?> attributes = element.attributes.elements();
		while (attributes.hasMoreElements()) {
			DTDAttribute atr = (DTDAttribute) attributes.nextElement();

			String atrName = ParseTools.clean(atr.name);
			String type = _commentMapper.getNativeType(ParseTools.clean(element.name) + "." + atrName, "");
			
			dataClazz.addToMembers(atrName, type, ITemplateClass.VAR_TYPE_NATIVE);
			dataClazz.addToConstructor(atrName, type, ITemplateClass.VAR_TYPE_NATIVE);
			dataClazz.addToMethods(atrName, type, ITemplateClass.VAR_TYPE_NATIVE);

			readerClazz.addAttributeToReadMethod(atrName, ParseTools.getIsImplied(atr), type);
		}
	}
}